//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 1995-2000, The Hypersonic SQL Group.
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Hypersonic SQL Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the Hypersonic SQL Group.
 *
 *
 * For work added by the HSQL Development Group:
 *
 * Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
using EffiProz.Core.Lib;
using EffiProz.Core.RowIO;





// fredt@users 20020130 - patch 1.7.0 by fredt
// to ensure consistency of r.rTail r.iSize _in all operations
// methods for set operations moved here from Select.java
// tony_lai@users 20020820 - patch 595073 - duplicated exception msg
// fredt@users 20030801 - patch 1.7.2 - separate metadata and polymophic serialisation
// boucherb@users 200307/8 - various, _in support of fred's work over the same time period

/**
 *  The primary unit of comunication between Connection, Server and Session
 *  objects.
 *
 *  An HSQLDB Result object encapsulates all requests (such as to alter or
 *  query session settings, to allocate and execute statements, etc.) and all
 *  responses (such as exception indications, update counts, result sets and
 *  result set metadata). It also : the HSQL wire protocol for
 *  comunicating all such requests and responses across the network.
 *
 * Extensively rewritten and extended _in successive versions of HSQLDB.
 *
 * @author Thomas Mueller (Hypersonic SQL Group)
 * @version 1.8.0
 * @since Hypersonic SQL
 */

namespace EffiProz.Core
{

    public class Result : IEnumerable<object[]>
    {

        // record list
        public Record rRoot;
        private Record rTail;
        private int size;

        // transient - number of significant columns
        private int significantColumns;

        // type of result
        public int mode;

        //    bool isMulti;
        // database ID
        public int databaseID;

        // session ID
        public int sessionID;

        // user / password or error strings
        public string mainString;
        public string subString;

        // database name
        public string subSubString;

        // the exception if this is an error
        private Exception exception;

        // prepared statement id / error vendor code
        public int statementID;

        // max rows (out) or update count (in)
        public int updateCount = -1;
        public ResultMetaData metaData;

        public IEnumerator<object[]> GetEnumerator()
        {
            return new ResultIterator(this);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new ResultIterator(this);
        }


        /** A Result object's metadata */
        public class ResultMetaData
        {

            // always resolved
            public String[] colLabels;
            public String[] tableNames;
            public String[] colNames;
            public bool[] isLabelQuoted;
            public int[] colTypes;
            public int[] colSizes;
            public int[] colScales;

            // extra attrs, sometimes resolved
            public String[] catalogNames;
            public String[] schemaNames;
            public int[] colNullable;
            public bool[] isIdentity;
            public bool[] isWritable;
            public int[] paramMode;

            // It's possible to do better than java.lang.Object
            // for type OTHER if the expression generating the value
            // is of type FUNCTION.  This applies to result set columns
            // whose value is the result of a SQL function call and
            // especially to the arguments and return value of a CALL
            public String[] classNames;
            public bool isParameterDescription;

            public ResultMetaData() { }

            public ResultMetaData(int n)
            {
                prepareData(n);
            }

            /**
             *  Method declaration
             *
             * @param  columns
             */
            public void prepareData(int columns)
            {

                colLabels = new String[columns];
                tableNames = new String[columns];
                colNames = new String[columns];
                isLabelQuoted = new bool[columns];
                colTypes = new int[columns];
                colSizes = new int[columns];
                colScales = new int[columns];
                catalogNames = new String[columns];
                schemaNames = new String[columns];
                colNullable = new int[columns];
                isIdentity = new bool[columns];
                isWritable = new bool[columns];
                classNames = new String[columns];
            }

            public int[] getParameterTypes()
            {
                return colTypes;
            }

            public bool isTableColumn(int i)
            {
                return tableNames[i] != null && tableNames[i].Length > 0
                       && colNames[i] != null && colNames[i].Length > 0;
            }

            private void decodeTableColumnAttrs(int _in, int i)
            {

                colNullable[i] = _in & 0x0000000f;
                isIdentity[i] = (_in & 0x00000010) != 0;
                isWritable[i] = (_in & 0x00000020) != 0;
            }

            private void writeTableColumnAttrs(RowOutputBinary _out,
                                               int i)
            {

                // HSQLDB also ignores precision and scale for all types except
                // XXXCHAR, for which it may (or may not) perform some trimming/padding.
                // All _in all, it's currently meaningless (indeed misleading) to
                // transmit and report the values, as the data typically will
                // not be constrained accordingly.
                //        switch(colType[i]) {
                //            // As early as SQL 92, these are allowed to have a scale.
                //            // However, DatabaseCommandInterpreter.processCreateColumn
                //            // does not currently handle this correctly and will assign
                //            // a precision instead of a scale if TIME(s) or TIMESTAMP(s)
                //            // is specified
                //            case Types.TIME :
                //            case Types.TIMESTAMP :
                //                  _out.writeIntData(colScale[i]);
                //                  break;
                //            case Types.DECIMAL :
                //            case Types.NUMERIC : {
                //                _out.writeIntData(colScale[i]);
                //            } // fall through
                //            // Apparently, SQL 92 specifies that FLOAT can have
                //            // a declared precision, which is typically the number of
                //            // bits (not binary digits).  In any case, this is somewhat
                //            // meaningless under HSQLDB/Java, _in that we use java.lang.Double
                //            // to represent SQL FLOAT
                //            case Types.FLOAT :
                //            // It's legal to declare precision for these, although HSQLDB
                //            // currently does not use it to constrain values
                //            case Types.BINARY :
                //            case Types.VARBINARY :
                //            case Types.LONGVARBINARY :
                //            // possibly, but not universally acted upon (trimmming/padding)
                //            case Types.CHAR  :
                //            case Types.VARCHAR :
                //            case Types.LONGVARCHAR : {
                //                _out.writeIntData(colSize[i]);
                //            }
                //        }
                _out.writeIntData(encodeTableColumnAttrs(i));
                _out.writeString(catalogNames[i] == null ? ""
                                                        : catalogNames[i]);
                _out.writeString(schemaNames[i] == null ? ""
                                                       : schemaNames[i]);
            }

            private int encodeTableColumnAttrs(int i)
            {

                int _out = colNullable[i];    // always between 0x00 and 0x02

                if (isIdentity[i])
                {
                    _out |= 0x00000010;
                }

                if (isWritable[i])
                {
                    _out |= 0x00000020;
                }

                return _out;
            }

            private void readTableColumnAttrs(RowInputBinary _in,
                                              int i)
            {

                decodeTableColumnAttrs(_in.readIntData(), i);

                catalogNames[i] = _in.readString();
                schemaNames[i] = _in.readString();
            }

            public ResultMetaData(RowInputBinary _in,
                           int mode)
            {

                int l = _in.readIntData();

                prepareData(l);

                if (mode == ResultConstants.PARAM_META_DATA)
                {
                    isParameterDescription = true;
                    paramMode = new int[l];
                }

                for (int i = 0; i < l; i++)
                {
                    colTypes[i] = _in.readType();

                    // fredt - 1.8.0 added
                    colSizes[i] = _in.readIntData();
                    colScales[i] = _in.readIntData();
                    colLabels[i] = _in.readString();
                    tableNames[i] = _in.readString();
                    colNames[i] = _in.readString();
                    classNames[i] = _in.readString();

                    if (isTableColumn(i))
                    {
                        readTableColumnAttrs(_in, i);
                    }

                    if (mode == ResultConstants.PARAM_META_DATA)
                    {
                        paramMode[i] = _in.readIntData();
                    }
                }
            }

            public void write(RowOutputBinary _out,
                       int colCount)
            {

                _out.writeIntData(colCount);

                for (int i = 0; i < colCount; i++)
                {
                    _out.writeType(colTypes[i]);

                    // fredt - 1.8.0 added
                    _out.writeIntData(colSizes[i]);
                    _out.writeIntData(colScales[i]);
                    _out.writeString(colLabels[i] == null ? ""
                                                         : colLabels[i]);
                    _out.writeString(tableNames[i] == null ? ""
                                                          : tableNames[i]);
                    _out.writeString(colNames[i] == null ? ""
                                                        : colNames[i]);
                    _out.writeString(classNames[i] == null ? ""
                                                          : classNames[i]);

                    if (isTableColumn(i))
                    {
                        writeTableColumnAttrs(_out, i);
                    }

                    if (isParameterDescription)
                    {
                        _out.writeIntData(paramMode[i]);
                    }
                }
            }
        }

        /**
         *  General constructor
         */
        public Result(int type)
        {

            mode = type;

            /*
                    if (type == ResultConstants.MULTI) {
                        isMulti = true;
                    }
            */
            if (type == ResultConstants.DATA
                    || type == ResultConstants.PARAM_META_DATA
                    || type == ResultConstants.SQLEXECUTE
                    || type == ResultConstants.SETSESSIONATTR)
            {
                metaData = new ResultMetaData();
            }
        }

        public Result(ResultMetaData md)
        {

            mode = ResultConstants.DATA;
            significantColumns = md.colTypes.Length;
            metaData = md;
        }

        // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)

        /**
         *  Constructor for errors
         *
         * @param  error error message
         * @param  state   sql state
         * @param  code   vendor code
         */
        public Result(string error, string state, int code)
        {

            mode = ResultConstants.ERROR;
            mainString = error;
            subString = state;
            statementID = code;
            subSubString = "";
        }

        /**
         *  Only used with DATA and PARAM_META_DATA results
         *
         * @param  columns
         */
        public Result(int type, int columns)
        {

            metaData = new ResultMetaData();

            metaData.prepareData(columns);

            if (type == ResultConstants.PARAM_META_DATA)
            {
                metaData.isParameterDescription = true;
                metaData.paramMode = new int[columns];
            }

            mode = type;
            significantColumns = columns;
        }

        /**
         * For BATCHEXECUTE and BATCHEXECDIRECT
         */
        public Result(int type, int[] types, int id)
        {

            mode = type;
            metaData = new ResultMetaData();
            metaData.colTypes = types;
            significantColumns = types.Length;
            statementID = id;
        }

        /**
         *  Constructor declaration
         *
         * @param  in
         * @exception  HsqlException  Description of the Exception
         */
        public Result(RowInputBinary _in)
        {

            try
            {
                mode = _in.readIntData();

                if (mode == ResultConstants.MULTI)
                {
                    readMultiResult(_in);

                    return;
                }

                databaseID = _in.readIntData();
                sessionID = _in.readIntData();

                switch (mode)
                {

                    case ResultConstants.GETSESSIONATTR:
                    case ResultConstants.SQLDISCONNECT:
                    case ResultConstants.SQLSTARTTRAN:
                    case ResultConstants.HSQLRESETSESSION:
                        break;

                    case ResultConstants.SQLPREPARE:
                        setStatementType(_in.readIntData());

                        mainString = _in.readString();
                        break;

                    case ResultConstants.PREPARE_ACK:
                    case ResultConstants.SQLFREESTMT:
                        statementID = _in.readIntData();
                        break;

                    case ResultConstants.SQLEXECDIRECT:
                        updateCount = _in.readIntData();
                        statementID = _in.readIntData();
                        mainString = _in.readString();
                        break;

                    case ResultConstants.ERROR:
                    case ResultConstants.SQLCONNECT:
                        mainString = _in.readString();
                        subString = _in.readString();
                        subSubString = _in.readString();
                        statementID = _in.readIntData();

                        //                    throw Trace.getError(string, code);
                        break;

                    case ResultConstants.UPDATECOUNT:
                        updateCount = _in.readIntData();
                        break;

                    case ResultConstants.SQLENDTRAN:
                        {
                            int type = _in.readIntData();

                            setEndTranType(type);                    // endtran type

                            switch (type)
                            {

                                case ResultConstants.SAVEPOINT_NAME_RELEASE:
                                case ResultConstants.SAVEPOINT_NAME_ROLLBACK:
                                    mainString = _in.readString();    // savepoint name
                                    break;
                            }

                            break;
                        }
                    case ResultConstants.BATCHEXECUTE:
                    case ResultConstants.BATCHEXECDIRECT:
                    case ResultConstants.SQLEXECUTE:
                    case ResultConstants.SETSESSIONATTR:
                        {
                            updateCount = _in.readIntData();
                            statementID = _in.readIntData();

                            int l = _in.readIntData();

                            metaData = new ResultMetaData(l);
                            significantColumns = l;

                            for (int i = 0; i < l; i++)
                            {
                                metaData.colTypes[i] = _in.readType();
                            }

                            int count = _in.readIntData();

                            while (count-- > 0)
                            {
                                add(_in.readData(metaData.colTypes));
                            }

                            break;
                        }
                    case ResultConstants.DATA:
                    case ResultConstants.PARAM_META_DATA:
                        {
                            metaData = new ResultMetaData(_in, mode);
                            significantColumns = metaData.colLabels.Length;

                            int count = _in.readIntData();

                            while (count-- > 0)
                            {
                                add(_in.readData(metaData.colTypes));
                            }

                            break;
                        }
                    case ResultConstants.SQLSETCONNECTATTR:
                        {
                            int type = _in.readIntData();             // attr type

                            setConnectionAttrType(type);

                            switch (type)
                            {

                                case ResultConstants.SQL_ATTR_SAVEPOINT_NAME:
                                    mainString = _in.readString();    // savepoint name
                                    break;

                                //  case ResultConstants.SQL_ATTR_AUTO_IPD :
                                //      - always true
                                //  default: throw - case never happens
                            }

                            break;
                        }
                    default:
                        throw new CoreException(
                            Trace.getMessage(
                                Trace.Result_Result, true, new Object[]{
                                  (mode) }), null, 0);
                }
            }
            catch (IOException)
            {
                throw Trace.error(Trace.TRANSFER_CORRUPTED);
            }
        }

        public static Result newSingleColumnResult(string colName, int colType)
        {

            Result result = new Result(ResultConstants.DATA, 1);

            result.metaData.colNames[0] = colName;
            result.metaData.colLabels[0] = colName;
            result.metaData.tableNames[0] = "";
            result.metaData.colTypes[0] = colType;

            return result;
        }

        public static Result newPrepareResponse(int csid, Result rsmd, Result pmd)
        {

            Result _out;
            Result pack;

            _out = new Result(ResultConstants.MULTI);

            //        _out.isMulti      = true;
            pack = new Result(ResultConstants.PREPARE_ACK);
            pack.statementID = csid;

            _out.add(new Object[] { pack });
            _out.add(new Object[] { rsmd });
            _out.add(new Object[] { pmd });

            return _out;
        }

        public static Result newParameterDescriptionResult(int len)
        {

            Result r = new Result(ResultConstants.PARAM_META_DATA, len);

            r.metaData.isParameterDescription = true;
            r.metaData.paramMode = new int[len];

            return r;
        }

        public static Result newFreeStmtRequest(int statementID)
        {

            Result r = new Result(ResultConstants.SQLFREESTMT);

            r.statementID = statementID;

            return r;
        }

        static Result newExecuteDirectRequest(string sql)
        {

            Result _out;

            _out = new Result(ResultConstants.SQLEXECDIRECT);

            _out.setMainString(sql);

            return _out;
        }

        public static Result newReleaseSavepointRequest(string name)
        {

            Result _out;

            _out = new Result(ResultConstants.SQLENDTRAN);

            _out.setMainString(name);
            _out.setEndTranType(ResultConstants.SAVEPOINT_NAME_RELEASE);

            return _out;
        }

        public static Result newCommitRequest()
        {

            Result _out;

            _out = new Result(ResultConstants.SQLENDTRAN);

            _out.setMainString("");
            _out.setEndTranType(ResultConstants.COMMIT);

            return _out;
        }

        public static Result newPrepareCommitRequest()
        {

            Result _out;

            _out = new Result(ResultConstants.SQLENDTRAN);

            _out.setMainString("");
            _out.setEndTranType(ResultConstants.HSQLPREPARECOMMIT);

            return _out;
        }

        public static Result newRollbackToSavepointRequest(string name)
        {

            Result _out;

            _out = new Result(ResultConstants.SQLENDTRAN);

            _out.setMainString(name);
            _out.setEndTranType(ResultConstants.SAVEPOINT_NAME_ROLLBACK);

            return _out;
        }

        public static Result newSetSavepointRequest(string name)
        {

            Result _out;

            _out = new Result(ResultConstants.SQLSETCONNECTATTR);

            _out.setConnectionAttrType(ResultConstants.SQL_ATTR_SAVEPOINT_NAME);
            _out.setMainString(name);

            return _out;
        }

        /**
         *  Method declaration
         *
         * @return
         */
        public int getSize()
        {
            return size;
        }

        /**
         *  Method declaration
         *
         * @param  columns
         */
        public void setColumnCount(int columns)
        {
            significantColumns = columns;
        }

        /**
         *  Method declaration
         *
         * @return
         */
        public int getColumnCount()
        {
            return significantColumns;
        }

        /**
         *  Append Result argument to this.
         *
         * @param  a
         */
        public void append(Result a)
        {

            if (a.rRoot == null)
            {
                return;
            }

            if (rRoot == null)
            {
                rRoot = a.rRoot;
            }
            else
            {
                rTail.next = a.rRoot;
            }

            rTail = a.rTail;
            size += a.size;
        }

        public void addAll(Result r)
        {

            if (r == null)
            {
                return;
            }

            Record from = r.rRoot;

            while (from != null)
            {
                add(from.data);

                from = from.next;
            }
        }

        public void clear()
        {

            rRoot = null;
            rTail = null;
            size = 0;
        }

        public bool isEmpty()
        {
            return rRoot == null;
        }

        /**
         *  Method declaration
         *
         * @param  a
         */
        public void setRows(Result a)
        {

            if (a == null)
            {
                rRoot = null;
                rTail = null;
                size = 0;
            }
            else
            {
                rRoot = a.rRoot;
                rTail = a.rTail;
                size = a.size;
            }
        }

        /**
         *  Method declaration
         *
         * @param  d
         */
        public void add(Object[] d)
        {

            Record r = new Record();

            r.data = d;

            if (rRoot == null)
            {
                rRoot = r;
            }
            else
            {
                rTail.next = r;
            }

            rTail = r;

            size++;
        }

        /**
         *  Method declaration
         *
         * @param  limitstart  number of records to discard at the head
         * @param  limitcount  number of records to keep, all the rest if 0
         */

        // fredt@users 20020130 - patch 1.7.0 by fredt
        // rewritten and moved from Select.java
        public void trimResult(int limitstart, int limitcount)
        {

            Record n = rRoot;

            if (n == null)
            {
                return;
            }

            if (limitstart >= size)
            {
                size = 0;
                rRoot = rTail = null;

                return;
            }

            size -= limitstart;

            for (int i = 0; i < limitstart; i++)
            {
                n = n.next;

                if (n == null)
                {

                    // if iSize is consistent this block will never be reached
                    size = 0;
                    rRoot = rTail = n;

                    return;
                }
            }

            rRoot = n;

            if (limitcount == 0 || limitcount >= size)
            {
                return;
            }

            for (int i = 1; i < limitcount; i++)
            {
                n = n.next;

                if (n == null)
                {

                    // if iSize is consistent this block will never be reached
                    return;
                }
            }

            size = limitcount;
            n.next = null;
            rTail = n;
        }

        /**
         * Removes duplicate rows on the basis of comparing the singificant
         * columns of the rows _in the result.
         *
         * @throws  HsqlException
         */
        public void removeDuplicates(Session session)
        {
            removeDuplicates(session, significantColumns);
        }

        /**
         * Removes duplicate rows on the basis of comparing the first columnCount
         * columns of rows _in the result.
         *
         * @throws  HsqlException
         */

        // fredt@users 20020130 - patch 1.7.0 by fredt
        // to ensure consistency of r.rTail r.iSize _in all set operations
        public void removeDuplicates(Session session,
                              int columnCount)
        {

            if (rRoot == null)
            {
                return;
            }

            int[] order = new int[columnCount];
            int[] way = new int[columnCount];

            for (int i = 0; i < columnCount; i++)
            {
                order[i] = i;
                way[i] = 1;
            }

            sortResult(session, order, way);

            Record n = rRoot;

            for (; ; )
            {
                Record next = n.next;

                if (next == null)
                {
                    break;
                }

                if (compareRecord(session, n.data, next.data, columnCount) == 0)
                {
                    n.next = next.next;

                    size--;
                }
                else
                {
                    n = next;
                }
            }

            rTail = n;
        }

        /**
         *  Removes duplicates then removes the contents of the second result
         *  from this one base on first columnCount of the rows _in each result.
         *
         * @param  minus
         * @throws  HsqlException
         */
        public void removeSecond(Session session, Result minus,
                          int columnCount)
        {

            removeDuplicates(session, columnCount);
            minus.removeDuplicates(session, columnCount);

            Record n = rRoot;
            Record last = rRoot;
            bool rootr = true;    // checking rootrecord
            Record n2 = minus.rRoot;
            int i = 0;

            while (n != null && n2 != null)
            {
                i = compareRecord(session, n.data, n2.data, columnCount);

                if (i == 0)
                {
                    if (rootr)
                    {
                        rRoot = last = n.next;
                    }
                    else
                    {
                        last.next = n.next;
                    }

                    n = n.next;

                    size--;
                }
                else if (i > 0)
                {    // r > minus
                    n2 = n2.next;
                }
                else
                {               // r < minus
                    last = n;
                    rootr = false;
                    n = n.next;
                }
            }

            for (; n != null; )
            {
                last = n;
                n = n.next;
            }

            rTail = last;
        }

        /**
         * Removes all duplicate rows then removes all rows that are not shared
         * between this and the other result, based on comparing the first
         * columnCount columns of each result.
         *
         * @param  r2
         * @throws  HsqlException
         */
        public void removeDifferent(Session session, Result r2,
                             int columnCount)
        {

            removeDuplicates(session, columnCount);
            r2.removeDuplicates(session, columnCount);

            Record n = rRoot;
            Record last = rRoot;
            bool rootr = true;    // checking rootrecord
            Record n2 = r2.rRoot;
            int i = 0;

            size = 0;

            while (n != null && n2 != null)
            {
                i = compareRecord(session, n.data, n2.data, columnCount);

                if (i == 0)
                {             // same rows
                    if (rootr)
                    {
                        rRoot = n;        // make this the first record
                    }
                    else
                    {
                        last.next = n;    // this is next record _in resultset
                    }

                    rootr = false;
                    last = n;            // this is last record _in resultset
                    n = n.next;
                    n2 = n2.next;

                    size++;
                }
                else if (i > 0)
                {       // r > r2
                    n2 = n2.next;
                }
                else
                {                  // r < r2
                    n = n.next;
                }
            }

            if (rootr)
            {             // if no lines _in resultset
                rRoot = null;        // then return null
                last = null;
            }
            else
            {
                last.next = null;    // else end resultset
            }

            rTail = last;
        }

        /**
         *  Method declaration
         *
         * @param  order
         * @param  way
         * @throws  HsqlException
         */
        public void sortResult(Session session, int[] order,
                         int[] way)
        {

            if (rRoot == null || rRoot.next == null)
            {
                return;
            }

            Record source0, source1;
            Record[] target = new Record[2];
            Record[] targetlast = new Record[2];
            int dest = 0;
            Record n = rRoot;

            while (n != null)
            {
                Record next = n.next;

                n.next = target[dest];
                target[dest] = n;
                n = next;
                dest ^= 1;
            }

            for (int blocksize = 1; target[1] != null; blocksize <<= 1)
            {
                source0 = target[0];
                source1 = target[1];
                target[0] = target[1] = targetlast[0] = targetlast[1] = null;

                for (dest = 0; source0 != null; dest ^= 1)
                {
                    int n0 = blocksize,
                        n1 = blocksize;

                    while (true)
                    {
                        if (n0 == 0 || source0 == null)
                        {
                            if (n1 == 0 || source1 == null)
                            {
                                break;
                            }

                            n = source1;
                            source1 = source1.next;

                            n1--;
                        }
                        else if (n1 == 0 || source1 == null)
                        {
                            n = source0;
                            source0 = source0.next;

                            n0--;
                        }
                        else if (compareRecord(session, source0.data, source1
                              .data, order, way) > 0)
                        {
                            n = source1;
                            source1 = source1.next;

                            n1--;
                        }
                        else
                        {
                            n = source0;
                            source0 = source0.next;

                            n0--;
                        }

                        if (target[dest] == null)
                        {
                            target[dest] = n;
                        }
                        else
                        {
                            targetlast[dest].next = n;
                        }

                        targetlast[dest] = n;
                        n.next = null;
                    }
                }
            }

            rRoot = target[0];
            rTail = targetlast[0];
        }

        /**
         *  Method declaration
         *
         * @param  a
         * @param  b
         * @param  order
         * @param  way
         * @return -1, 0, +1
         * @throws  HsqlException
         */
        private int compareRecord(Session session, Object[] a, Object[] b,
                                   int[] order,
                                  int[] way)
        {

            int i = Column.compare(session.database.collation, a[order[0]],
                                   b[order[0]], metaData.colTypes[order[0]]);

            if (i == 0)
            {
                for (int j = 1; j < order.Length; j++)
                {
                    i = Column.compare(session.database.collation, a[order[j]],
                                       b[order[j]], metaData.colTypes[order[j]]);

                    if (i != 0)
                    {
                        return i * way[j];
                    }
                }
            }

            return i * way[0];
        }

        /**
         *  Method declaration
         *
         * @param  a
         * @param  b
         * @param  len
         * @return -1, 0, +1
         * @throws  HsqlException
         */
        private int compareRecord(Session session, Object[] a, Object[] b,
                                  int len)
        {

            for (int j = 0; j < len; j++)
            {
                int i = Column.compare(session.database.collation, a[j], b[j],
                                       metaData.colTypes[j]);

                if (i != 0)
                {
                    return i;
                }
            }

            return 0;
        }

        /**
         * Result structure used for set/get session attributes
         */
        public static Result newSessionAttributesResult()
        {

            Result r = new Result(ResultConstants.DATA, 7);

            r.metaData.colNames = r.metaData.colLabels = r.metaData.tableNames =
                new String[] {
            "", "", "", "", "", "", ""
        };
            r.metaData.colTypes = new int[] {
            Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.INTEGER,
            Types.BOOLEAN, Types.BOOLEAN, Types.BOOLEAN
        };

            return r;
        }

        public void write(RowOutputBinary _out)
        {

            if (mode == ResultConstants.MULTI)
            {
                writeMulti(_out);

                return;
            }

            int startPos = _out.size();

            _out.writeSize(0);
            _out.writeIntData(mode);
            _out.writeIntData(databaseID);
            _out.writeIntData(sessionID);

            switch (mode)
            {

                case ResultConstants.GETSESSIONATTR:
                case ResultConstants.SQLDISCONNECT:
                case ResultConstants.SQLSTARTTRAN:
                case ResultConstants.HSQLRESETSESSION:
                    break;

                case ResultConstants.SQLPREPARE:

                    // Allows the engine side to fast-fail prepare of non-CALL
                    // statement against a CallableStatement object and CALL
                    // statement against PreparedStatement.
                    //
                    // May be useful _in the future for other things
                    _out.writeIntData(getStatementType());
                    _out.writeString(mainString);
                    break;

                case ResultConstants.PREPARE_ACK:
                case ResultConstants.SQLFREESTMT:
                    _out.writeIntData(statementID);
                    break;

                case ResultConstants.SQLEXECDIRECT:
                    _out.writeIntData(updateCount);
                    _out.writeIntData(statementID);          // currently unused
                    _out.writeString(mainString);
                    break;

                case ResultConstants.ERROR:
                case ResultConstants.SQLCONNECT:
                    _out.writeString(mainString);
                    _out.writeString(subString);
                    _out.writeString(subSubString);
                    _out.writeIntData(statementID);
                    break;

                case ResultConstants.UPDATECOUNT:
                    _out.writeIntData(updateCount);
                    break;

                case ResultConstants.SQLENDTRAN:
                    {
                        int type = getEndTranType();

                        _out.writeIntData(type);                 // endtran type

                        switch (type)
                        {

                            case ResultConstants.SAVEPOINT_NAME_RELEASE:
                            case ResultConstants.SAVEPOINT_NAME_ROLLBACK:
                                _out.writeString(mainString);    // savepoint name
                                break;
                            // default; // do nothing
                        }

                        break;
                    }
                case ResultConstants.BATCHEXECUTE:
                case ResultConstants.BATCHEXECDIRECT:
                case ResultConstants.SQLEXECUTE:
                case ResultConstants.SETSESSIONATTR:
                    {
                        _out.writeIntData(updateCount);
                        _out.writeIntData(statementID);

                        int l = significantColumns;

                        _out.writeIntData(l);

                        for (int i = 0; i < l; i++)
                        {
                            _out.writeType(metaData.colTypes[i]);
                        }

                        _out.writeIntData(size);

                        Record n = rRoot;

                        while (n != null)
                        {
                            _out.writeData(l, metaData.colTypes, n.data, null, null);

                            n = n.next;
                        }

                        break;
                    }
                case ResultConstants.DATA:
                case ResultConstants.PARAM_META_DATA:
                    {
                        metaData.write(_out, significantColumns);
                        _out.writeIntData(size);

                        Record n = rRoot;

                        while (n != null)
                        {
                            _out.writeData(significantColumns, metaData.colTypes,
                                          n.data, null, null);

                            n = n.next;
                        }

                        break;
                    }
                case ResultConstants.SQLSETCONNECTATTR:
                    {
                        int type = getConnectionAttrType();

                        _out.writeIntData(type);                 // attr type

                        switch (type)
                        {

                            case ResultConstants.SQL_ATTR_SAVEPOINT_NAME:
                                _out.writeString(mainString);    // savepoint name
                                break;
                            // case ResultConstants.SQL_ATTR_AUTO_IPD // always true
                            // default: // throw, but case never happens
                        }

                        break;
                    }
                default:
                    throw new CoreException(
                        Trace.getMessage(
                            Trace.Result_Result, true, new Object[]{
                              (mode) }), null, 0);
            }

            _out.writeIntData(_out.size(), startPos);
        }

        public void readMultiResult(RowInputBinary _in)
        {

            mode = ResultConstants.MULTI;
            databaseID = _in.readIntData();
            sessionID = _in.readIntData();

            int count = _in.readIntData();

            for (int i = 0; i < count; i++)
            {

                // Currently required for the outer result, but can simply
                // be ignored for sub-results
                _in.readIntData();
                add(new Object[] { new Result(_in) });
            }
        }

        private void writeMulti(RowOutputBinary _out)
        {

            int startPos = _out.size();

            _out.writeSize(0);
            _out.writeIntData(mode);
            _out.writeIntData(databaseID);
            _out.writeIntData(sessionID);
            _out.writeIntData(size);

            Record n = rRoot;

            while (n != null)
            {
                ((Result)n.data[0]).write(_out);

                n = n.next;
            }

            _out.writeIntData(_out.size(), startPos);
        }

        /**
         * Convenience method for writing, shared by Server side.
         */
        public static void write(Result r, RowOutputBinary rowout,
                                 Stream dataout)
        {

            rowout.reset();
            r.write(rowout);
            dataout.Write(rowout.getOutputStream().getBuffer(), 0,
                          rowout.getOutputStream().size());
            dataout.Flush();
        }

        /**
         * Convenience method for reading, shared by Server side.
         */
        public static Result read(RowInputBinary rowin,
                                  Stream datain)
        {

            byte[] tmp = new byte[4];
            datain.Read(tmp, 0, 4);

            int length = BitConverter.ToInt32(tmp, 0);

            rowin.resetRow(0, length);

            byte[] byteArray = rowin.getBuffer();
            int offset = 4;

            datain.Read(byteArray, offset, length - offset);

            return new Result(rowin);
        }

        /** @todo fredt - move the messages to Trace.java */
        public Result(Exception t, string statement)
        {

            mode = ResultConstants.ERROR;
            exception = t;

            if (t is CoreException)
            {
                CoreException he = (CoreException)t;

                subString = he.getSQLState();
                mainString = he.Message;

                if (statement != null)
                {
                    mainString += " _in statement [" + statement + "]";
                }

                statementID = he.getErrorCode();
            }
            else if (t is OutOfMemoryException)
            {

                // At this point, we've nothing to lose by doing this
                System.GC.Collect();

                subString = "S1000";
                mainString = "out of memory";
                statementID = Trace.OUT_OF_MEMORY;
            }
            else
            {
                subString = "S1000";
                mainString = Trace.getMessage(Trace.GENERAL_ERROR) + " " + t;

                if (statement != null)
                {
                    mainString += " _in statement [" + statement + "]";
                }

                statementID = Trace.GENERAL_ERROR;
            }

            subSubString = "";
        }

        public Exception getException()
        {
            return exception;
        }

        public int getStatementID()
        {
            return statementID;
        }

        public void setStatementID(int id)
        {
            statementID = id;
        }

        public string getMainString()
        {
            return mainString;
        }

        public void setMainString(string sql)
        {
            mainString = sql;
        }

        public string getSubString()
        {
            return subString;
        }

        public void setMaxRows(int count)
        {
            updateCount = count;
        }

        public int getUpdateCount()
        {
            return updateCount;
        }

       public   int getConnectionAttrType()
        {
            return updateCount;
        }

       public void setConnectionAttrType(int type)
        {
            updateCount = type;
        }

       public int getEndTranType()
        {
            return updateCount;
        }

       public void setEndTranType(int type)
        {
            updateCount = type;
        }

        /** @todo fred - check this repurposing */
        public int[] getUpdateCounts()
        {
            return metaData.colTypes;
        }

        public Object[] getParameterData()
        {
            return (rRoot == null) ? null
                                   : rRoot.data;
        }

        public void setParameterData(Object[] data)
        {

            if (rRoot == null)
            {
                rRoot = new Record();
            }

            rRoot.data = data;
            rRoot.next = null;
            rTail = rRoot;
            size = 1;
        }

        public void setResultType(int type)
        {
            mode = type;
        }

        public void setStatementType(int type)
        {
            updateCount = type;
        }

        public int getStatementType()
        {
            return updateCount;
        }

        public int getType()
        {
            return mode;
        }

        public bool isData()
        {
            return mode == ResultConstants.DATA;
        }

        public bool isError()
        {
            return mode == ResultConstants.ERROR;
        }

        public bool isUpdateCount()
        {
            return mode == ResultConstants.UPDATECOUNT;
        }

        public Iterator iterator()
        {
            return new ResultIterator(this);
        }

        private class ResultIterator : Iterator, IEnumerator<object[]>
        {

            bool removed = false;
            int counter = 0;
            Record current = null;
            Record last = null;
            Result _o = null;

            public ResultIterator(Result _o)
            {
                this._o = _o;
                current = _o.rRoot;
            }

            public void Dispose() { }

            public object[] Current
            {
                get
                {
                    if (counter == 0)
                        counter++;
                    return current.data;
                }
            }

            object System.Collections.IEnumerator.Current
            {
                get
                {
                    if (counter == 0)
                        counter++;
                    return current;
                }
            }

            public  bool MoveNext()
            {
                if (hasNext())
                {
                    removed = false;

                    if (counter != 0)
                    {
                        last = current;
                        current = current.next;
                    }

                    counter++;

                    return true;
                }

                return false;
            }

            public  void Reset()
            {
                counter = 0;
            }

            public bool hasNext()
            {
                return counter < _o.size;
            }

            public Object next()
            {

                if (hasNext())
                {
                    removed = false;

                    if (counter != 0)
                    {
                        last = current;
                        current = current.next;
                    }

                    counter++;

                    return current.data;
                }


                throw new InvalidOperationException();
            }

            public int nextInt()
            {
                throw new InvalidOperationException();
            }

            public long nextLong()
            {
                throw new InvalidOperationException();
            }

            public void remove()
            {

                if (counter <= _o.size && counter != 0 && !removed)
                {
                    removed = true;

                    if (current == _o.rTail)
                    {
                        _o.rTail = last;
                    }

                    if (current == _o.rRoot)
                    {
                        current = _o.rRoot = _o.rRoot.next;
                    }
                    else
                    {
                        current = last;
                        last = null;
                        current.next = current.next.next;
                    }

                    _o.size--;
                    counter--;

                    return;
                }

                throw new InvalidOperationException();
            }
        }
    }
}
